########################################################################
#  Searx-Qt - Lightweight desktop application for Searx.
#  Copyright (C) 2020-2022  CYBERDEViL
#
#  This file is part of Searx-Qt.
#
#  Searx-Qt is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  Searx-Qt is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <https://www.gnu.org/licenses/>.
#
########################################################################

from uuid import uuid4

from PyQt5.QtWidgets import (
    QLabel,
    QDialog,
    QHBoxLayout,
    QVBoxLayout,
    QFormLayout,
    QListWidget,
    QComboBox,
    QLineEdit,
    QListWidgetItem,
    QCheckBox,
    QMessageBox
)

from PyQt5.QtCore import Qt, QVariant

from searxqt.widgets.buttons import Button

from searxqt.models.profiles import ProfileItem

from searxqt.translations import _


class AddProfileDialog(QDialog):
    def __init__(self, profiles, parent=None):
        QDialog.__init__(self, parent=parent)
        self.setWindowTitle(_("Add profile"))

        self._profiles = profiles
        self._id = None

        layout = QVBoxLayout(self)

        # Top message
        labelTxt = _("There are two types of profiles:\n"
                     "  1. stats2 profile\n"
                     "  2. user profile\n"
                     "\n"
                     "Choose stats2 profile type when you want to get a\n"
                     "list of searx-instances from a stats2-instance.\n"
                     "For example from the default https://searx.space\n"
                     "instance."
                     "\n"
                     "Choose user profile type when you want to manage\n"
                     "your own searx-instance's list.")
        label = QLabel(labelTxt, self)
        layout.addWidget(label)

        # Form layout
        formLayout = QFormLayout()
        layout.addLayout(formLayout)

        # Profile type
        self._typeBox = QComboBox(self)
        formLayout.addRow(QLabel(_("Type") + ":"), self._typeBox)
        # Do these in order to match InstancesModelTypes -1
        self._typeBox.addItem("stats2")
        self._typeBox.addItem("user")

        # Profile name
        self._nameEdit = QLineEdit(self)
        self._nameEdit.setMaxLength(32)
        formLayout.addRow(QLabel(_("Name") + ":"), self._nameEdit)

        # Profile preset
        from searxqt.defaults import Presets
        self._presetCombo = QComboBox(self)
        self._presetCombo.addItem("None")
        index = 1
        webIndex = 0
        for preset in Presets:
            if preset == "Web":
                webIndex = index
            index += 1
            self._presetCombo.addItem(preset)
        del Presets
        self._presetCombo.setCurrentIndex(webIndex)

        formLayout.addRow(QLabel(_("Preset") + ":"), self._presetCombo)

        # Feedback info label
        self._infoLabel = QLabel("", self)
        formLayout.addRow("", self._infoLabel)

        # Add / Cancel button
        buttonLayout = QHBoxLayout()
        layout.addLayout(buttonLayout)

        self._addButton = Button(_("Add"), self)
        self._addButton.setEnabled(False)
        buttonLayout.addWidget(self._addButton, 1, Qt.AlignRight)

        self._cancelButton = Button(_("Cancel"), self)
        buttonLayout.addWidget(self._cancelButton, 0, Qt.AlignRight)

        # Signals
        self._addButton.clicked.connect(self.__addButtonClicked)
        self._cancelButton.clicked.connect(self.reject)
        self._nameEdit.textEdited.connect(self.__nameEdited)

    def id(self): return self._id

    def __getSanitizedName(self):
        return self._nameEdit.text().rstrip().lstrip()

    def __addButtonClicked(self, state):
        self._id = str(uuid4())

        presetKey = self._presetCombo.currentText()
        preset = None if presetKey == "None" else presetKey

        profile = ProfileItem(
            self._id,
            self.__getSanitizedName(),
            # +1 to match InstancesModelTypes
            self._typeBox.currentIndex() + 1,
            preset=preset)
        self._profiles.add(profile)
        self.accept()

    def __doChecks(self):
        # Check if input name already exists.
        # profiles.conf is not re-read! So possible duplicate name if the
        # user where to add the same name at the same time in 2 different
        # instances of searx-qt.
        sanName = self.__getSanitizedName()
        if not sanName:
            self._addButton.setEnabled(False)
            self._infoLabel.setText("")
        elif sanName in self._profiles.names():
            self._addButton.setEnabled(False)
            self._infoLabel.setText(_("Name already exists."))
        else:
            self._addButton.setEnabled(True)
            self._infoLabel.setText("")

    def __nameEdited(self, name):
        self.__doChecks()


class ProfileChooserDialog(QDialog):
    def __init__(self, profiles, parent=None):
        QDialog.__init__(self, parent=parent)
        self.setWindowTitle(_("Profile select"))

        self._profiles = profiles
        self._items = {}
        self._selectedProfile = ProfileItem()
        self._activeProfiles = []

        # current profile index in the profile list (self._profileListWidget)
        self._currentIndex = -1

        # keep track of removed profile id's so their files can be
        # deleted on save.
        self._removedIds = []

        # keep track of new profile id's, these profiles don't have a
        # .conf file stored yet. When one of these id's get deleted we
        # don't have to remove the file because there is none.
        self._newIds = []

        layout = QVBoxLayout(self)

        # Top message
        labelTxt = _("Please select a profile")
        label = QLabel(labelTxt, self)
        layout.addWidget(label)

        hLayout = QHBoxLayout()
        layout.addLayout(hLayout)

        # Actions
        actionsLayout = QVBoxLayout()
        hLayout.addLayout(actionsLayout)

        self._addButton = Button(_("Add"), self)
        actionsLayout.addWidget(self._addButton, 0, Qt.AlignTop)

        self._delButton = Button(_("Delete"), self)
        self._delButton.setEnabled(False)
        actionsLayout.addWidget(self._delButton, 1, Qt.AlignTop)

        self._resetButton = Button(_("Force\nrelease\nprofiles"), self)
        self._resetButton.setEnabled(False)
        actionsLayout.addWidget(self._resetButton, 0, Qt.AlignBottom)

        # Profile list
        self._profileListWidget = QListWidget(self)
        hLayout.addWidget(self._profileListWidget)

        # Bottom stuff
        bottomLayout = QHBoxLayout()
        layout.addLayout(bottomLayout)

        self._defaultCheckBox = QCheckBox(_("Use as default"), self)
        bottomLayout.addWidget(self._defaultCheckBox, 1, Qt.AlignRight)

        exitButtonTxt = ""
        if profiles.current().id:
            exitButtonTxt = _("Cancel")
        else:
            exitButtonTxt = _("Exit")
        self._exitButton = Button(exitButtonTxt, self)
        bottomLayout.addWidget(self._exitButton, 0, Qt.AlignRight)

        self._selectButton = Button(_("Load profile"), self)
        self._selectButton.setEnabled(False)
        bottomLayout.addWidget(self._selectButton, 0, Qt.AlignRight)

        # Signals
        self._addButton.clicked.connect(self.__addButtonClicked)
        self._delButton.clicked.connect(self.__delButtonClicked)
        self._exitButton.clicked.connect(self.reject)
        self._selectButton.clicked.connect(self.__selectButtonClicked)
        self._profileListWidget.currentItemChanged.connect(self.__itemChanged)

        # Only show the 'force release profiles' button when there is
        # currently no profile set for this searx-qt instance.
        if not profiles.current().id:
            self._resetButton.clicked.connect(self.__resetButtonClicked)
        else:
            self._resetButton.hide()

        if not len(profiles):
            self.__addDialog()
        else:
            self.refresh()

    def selectedProfile(self): return self._selectedProfile
    def removedProfiles(self): return self._removedIds

    def promtProfileLoadFailed(self, msg):
        """
        @param msg: Message with what went wrong.
        @type msg: str
        """
        QMessageBox.warning(
            self,
            _("Failed to load profile."),
            msg
        )

    def __selectButtonClicked(self):
        item = self._profileListWidget.currentItem()
        selectedProfile = item.data(Qt.UserRole)

        profiles = self._profiles

        # Check if profile didn't get active in meantime.
        if profiles.profileActive(selectedProfile):
            self.promtProfileLoadFailed(
                _("Profile already in use."),
            )
            self.refresh()
            return

        # Check if profile still exists
        if (selectedProfile.id not in self._newIds and
                not profiles.profileExists(selectedProfile)):
            self.promtProfileLoadFailed(
                _("Profile doesn't exist anymore.")
            )
            self.refresh()
            return

        # Set default profile
        if self._defaultCheckBox.isChecked():
            self._profiles.setDefault(selectedProfile)
        else:
            self._profiles.setDefault(None)

        self._selectedProfile = selectedProfile

        self.accept()

    def __addDialog(self):
        dialog = AddProfileDialog(self._profiles)
        if dialog.exec():
            self._newIds.append(dialog.id())
            self.refresh()

    def __addButtonClicked(self):
        self.__addDialog()

    def __delButtonClicked(self):
        item = self._profileListWidget.currentItem()
        profile = item.data(Qt.UserRole)
        profiles = self._profiles

        # Confirmation
        confirmDialog = QMessageBox()
        confirmDialog.setWindowTitle(_(f"Delete profile {profile.name}"))
        confirmDialog.setText(_("Are you sure you want to delete profile " \
                                f"'{profile.name}'?"))
        confirmDialog.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
        confirmDialog.button(QMessageBox.Yes).setText(_("Yes"))
        confirmDialog.button(QMessageBox.No).setText(_("No"))

        if confirmDialog.exec() != QMessageBox.Yes:
            return

        if profiles.profileActive(profile):
            QMessageBox.warning(
                self,
                _("Failed to delete profile."),
                _("Cannot remove active profiles!")
            )
            self.refresh()
            return

        if (profiles.default() is not None and
            profiles.default().id == profile.id):
            # Default got removed.
            profiles.setDefault(None)

        # Remove profile from profiles.conf
        profiles.remove(profile)

        if profile.id in self._newIds:
            self._newIds.remove(profile.id)
        else:
            # Register the profile id as removed.
            self._removedIds.append(profile.id)

        self._profileListWidget.currentItemChanged.disconnect(
            self.__itemChanged
        )
        self.refresh()
        self._profileListWidget.currentItemChanged.connect(
            self.__itemChanged
        )

    def __resetButtonClicked(self):
        # Confirmation
        confirmDialog = QMessageBox()
        confirmDialog.setIcon(QMessageBox.Warning)
        confirmDialog.setWindowTitle(_("Force release active profiles"))
        confirmDialog.setText(
            _("This will force release all active profiles.\n"
              "\n"
              "This should only be run if Searx-Qt didn't exit properly\n"
              "and you can't access your profile anymore.\n"
              "\n"
              "Make sure to close all other running Searx-Qt instances\n"
              "before continuing!\n"
              "\n"
              "Do you want to continue?")
        )
        confirmDialog.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
        confirmDialog.button(QMessageBox.Yes).setText(_("Yes"))
        confirmDialog.button(QMessageBox.No).setText(_("No"))

        if confirmDialog.exec() != QMessageBox.Yes:
            return

        self._profiles.releaseAll()
        self.refresh()

    def __itemChanged(self, item, previousItem):
        if item:
            profile = item.data(Qt.UserRole)
            if (self._profiles.current().id == profile.id or
                    profile.id in self._activeProfiles):
                # Can't remove current profile
                self._delButton.setEnabled(False)
                self._selectButton.setEnabled(False)
            else:
                self._delButton.setEnabled(True)
                self._selectButton.setEnabled(True)
        else:
            self._delButton.setEnabled(False)
            self._selectButton.setEnabled(False)

        self._currentIndex = self._profileListWidget.currentRow()

    def refresh(self):
        self._profileListWidget.clear()
        self._items.clear()
        self._resetButton.setEnabled(False)

        profiles = self._profiles
        settings = profiles.settings()
        self._activeProfiles = profiles.getActiveProfiles(settings)

        for profile in profiles:
            itemTxt = f"{profile.type} - {profile.name}"
            item = QListWidgetItem()

            currentProfile = self._profiles.current()
            if currentProfile.id == profile.id:
                itemTxt = f"* {itemTxt}"

            item.setText(itemTxt)

            if profile.id in self._activeProfiles:
                item.setFlags(item.flags() & Qt.ItemIsSelectable)
                self._resetButton.setEnabled(True)

            item.setData(Qt.UserRole, QVariant(profile))
            self._items.update({profile.id: item})
            self._profileListWidget.addItem(item)

        # Restore index
        if self._currentIndex >= 0:
            self._currentIndex = min(
                self._currentIndex, self._profileListWidget.count() - 1
            )
            self._profileListWidget.setCurrentRow(self._currentIndex)
